﻿Option Strict On

Public Class FileList

    Public Enum FilenameParseOption As Short
        ReturnFilename = 1
        ReturnExtension = 2
    End Enum

    Public Enum FileUnitOption As Short
        Kilobytes = 1
        Megabytes = 2
        Gigabytes = 3
        Terabytes = 4
    End Enum

    Public Enum ItemParamaters As Short
        Null = 0
        Checked = 1
        Displayed = 2
        CheckedAndDisplayed = 3
    End Enum

    Public Const DefaultFileUnit As FileUnitOption = FileUnitOption.Megabytes

    Private oFileArray As ArrayList
    Private iDisplayedCount As Integer
    Private iSelectedCount As Integer = 0
    Private iUpperBound As Integer
    Private sParentDirectory As String

    'Private debugTimer As Stopwatch         'temporary variables for timing during testing only
    'Private iLoops As Integer

    ' ---TO DO---
    '   *   consider limiting search range within first and last matching entry
    '   *   investigate merge ListItemPath and ListItemFilename methods
    '   *   boundary checking for list sizes (main > search)/throw exception
    '   *   exception handling/throwing
    '   *   check requirement for sorting when list passed to HideOrphanedFiles - add details to comment
    '   *   investigate use of FileSizeFormatProvider
    ' -----------

    ''' <summary>
    ''' Creates new FileList instance from directory path.
    ''' </summary>
    ''' <param name="path">Path of directory containing files from which to create FileList.</param>
    ''' <param name="fileMask">File mask by which to filter files (format: *.xxx).</param>
    ''' <param name="params">Optional parameters to be applied to all files.</param>
    ''' <param name="recursive">Optional parameters to be applied to all files.</param>
    Public Sub New(ByVal path As String,
                   ByVal fileMask As String,
                   Optional ByVal params As ItemParamaters = ItemParamaters.Null,
                   Optional ByVal recursive As Boolean = False)

        Dim oSearchOption As IO.SearchOption

        If recursive Then oSearchOption = IO.SearchOption.AllDirectories Else oSearchOption = IO.SearchOption.TopDirectoryOnly
        sParentDirectory = path
        Dim oDirectory As IO.FileInfo() = New IO.DirectoryInfo(path).GetFiles(fileMask, oSearchOption)
        oFileArray = New ArrayList

        For Each oFile In oDirectory
            AddFile(oFile, params)
        Next

        UpdateParameter(params)

    End Sub

    ''' <summary>
    ''' Creates new FileList instance from DragEventArgs object.
    ''' </summary>
    ''' <param name="args">DragEventArgs object created on file drag/drop event from which to create FileList.</param>
    ''' <param name="fileMask">File mask by which to filter files (format: *.xxx).</param>
    ''' <param name="params">Optional parameters to be applied to all files.</param>
    ''' <param name="recursive">Optional parameters to be applied to all files.</param>
    Public Sub New(ByVal args As System.Windows.Forms.DragEventArgs,
                   ByVal fileMask As String,
                   Optional ByVal params As ItemParamaters = ItemParamaters.Null,
                   Optional ByVal recursive As Boolean = False)

        Dim multipleDirectories As Boolean = False
        Dim oFile As IO.FileInfo
        Dim oSearchOption As IO.SearchOption
        Dim sFiles() As String = DirectCast(args.Data.GetData(DataFormats.FileDrop), String())

        oFileArray = New ArrayList
        If recursive Then oSearchOption = IO.SearchOption.AllDirectories Else oSearchOption = IO.SearchOption.TopDirectoryOnly

        For i As Integer = 0 To sFiles.GetUpperBound(0)
            oFile = New IO.FileInfo(sFiles(i))

            If (oFile.Attributes And IO.FileAttributes.Directory) = IO.FileAttributes.Directory Then    'object is a directory
                If Not String.IsNullOrEmpty(sParentDirectory) AndAlso Not multipleDirectories Then multipleDirectories = True 'file list contains multiple directories
                sParentDirectory = oFile.FullName
                Dim oDirectory As IO.FileInfo() = New IO.DirectoryInfo(oFile.FullName).GetFiles(fileMask, oSearchOption)
                For Each oItem In oDirectory
                    AddFile(oItem, params)
                Next
            Else
                AddFile(oFile, params)
            End If
        Next

        If multipleDirectories Then sParentDirectory = vbNullString 'set parent directory to null as filearray contains multiple paths
        UpdateParameter(params)

    End Sub

    ''' <summary>
    ''' Creates new FileList instance using an array of IO.FileInfo objects.
    ''' </summary>
    ''' <param name="files">Array of IO.FileInfo objects from which to create FileList.</param>
    ''' <param name="params">Optional parameters to be applied to all files in the list.</param>
    Public Sub New(ByVal files() As IO.FileInfo,
                   Optional ByVal params As ItemParamaters = ItemParamaters.Null)

        oFileArray = New ArrayList

        For Each file In files
            AddFile(file, params)
        Next

        UpdateParameter(params)

    End Sub

    ''' <summary>
    ''' Creates new FileList instance from an ArrayList containing internal FileList structure.
    ''' </summary>
    ''' <param name="files">ArrayList containing internal FileList structure.</param>
    ''' <remarks>Doesn't set a value for ParentDirectory, but may not be needed</remarks>
    Private Sub New(ByVal files As ArrayList)

        oFileArray = New ArrayList(files)
        UpdateParameter()

    End Sub

    ''' <summary>
    ''' Adds individual files to FileList using an IO.FileInfo object.
    ''' </summary>
    ''' <param name="file">IO.FileInfo object to add to the FileList.</param>
    ''' <param name="params">Paramaters to apply to file.</param>
    Private Sub AddFile(ByVal file As IO.FileInfo,
                        ByVal params As Short)

        oFileArray.Add(params.ToString & My.Resources.CharListDelimiter & file.DirectoryName & My.Resources.CharPathDelimiter & file.Name & My.Resources.CharListDelimiter & file.Length.ToString)

    End Sub

    ''' <summary>
    ''' Returns the number of items in the FileList matching the specified parameters.
    ''' </summary>
    ''' <param name="params">Optional parameters by which to filter returned count.</param>
    ''' <returns>File count matching specified parameters.</returns>
    Public ReadOnly Property FileCount(Optional ByVal params As ItemParamaters = ItemParamaters.Null) As Integer

        Get
            Select Case params
                Case ItemParamaters.Null
                    Return iUpperBound + 1
                Case ItemParamaters.Checked
                    Return iSelectedCount
                Case ItemParamaters.Displayed
                    Return iDisplayedCount
                Case ItemParamaters.CheckedAndDisplayed
                    Dim iCheckedAndDisplayedCount As Integer    '*** MORE EFFICIENT WAY OF DOING THIS?

                    For i As Integer = 0 To iUpperBound
                        If (Parameters(i) And params) = params Then iCheckedAndDisplayedCount += 1
                    Next

                    Return iCheckedAndDisplayedCount
                Case Else
                    Return -1
            End Select
        End Get

    End Property

    ''' <summary>
    ''' Formats a given file size in bytes to a specifed unit, rounded to three significant digits.
    ''' </summary>
    ''' <param name="sizeInBytes">File size in bytes to be formatted.</param>
    ''' <param name="exponent">File size unit expressed as an exponent of 1024.</param>
    ''' <returns>File size and unit, rounded to three significant digits.</returns>
    ''' <remarks></remarks>
    Public Shared Function FormatFileSize(ByVal sizeInBytes As Long, Optional ByVal exponent As Short = 1) As String

        Dim iSizeInUnits As Double = sizeInBytes / (1024 ^ exponent)
        Dim oNumberFormat As New Globalization.NumberFormatInfo
        Dim sFileUnits As String() = Split(My.Resources.FileSizeUnits)

        oNumberFormat.NumberDecimalDigits = 0   'set number of decimal places to 0 by default

        If exponent > 1 Then
            If iSizeInUnits < 10 Then oNumberFormat.NumberDecimalDigits = 2 Else oNumberFormat.NumberDecimalDigits = 1 'round to three significant digits (0.00 or 00.0) for all units other than kilobytes
        End If

        Return iSizeInUnits.ToString("N", oNumberFormat) & My.Resources.CharSpace & sFileUnits(exponent) 'return string containing rounded file size and unit

    End Function

    ''' <summary>
    ''' Returns an ArrayList of unique file extensions contained within the FileList, in ascending order of frequency.
    ''' </summary>
    ''' <returns>ArrayList containing each unique file extensions contained within the list (format: *.xxx), sorted in ascending order of frequency.</returns>
    Public Function GetFileExtensions() As ArrayList

        Dim oExtensions As New ArrayList
        Dim sExtension As String

        For i As Integer = 0 To iUpperBound
            sExtension = ListItemFilename(i, FileList.FilenameParseOption.ReturnExtension)
            If sExtension IsNot Nothing Then oExtensions.Add(sExtension.ToUpper) 'add all non-null extensions to the array
        Next

        With oExtensions
            If .Count = 0 Then Return Nothing 'no files have extensions

            Dim iCurrentIndex As Integer = 0
            Dim iDelimeterIndex As Integer

            .Sort() 'initial alphebetical sort

            Do
                If oExtensions(iCurrentIndex) IsNot Nothing Then
                    Dim currentExtension As String = oExtensions(iCurrentIndex).ToString
                    Dim lastindex As Integer = .LastIndexOf(currentExtension)
                    Dim iExtensionCount As Integer = lastindex + 1 - iCurrentIndex

                    .RemoveRange(iCurrentIndex + 1, lastindex - iCurrentIndex)   'remove all except the first duplicate extension
                    oExtensions(iCurrentIndex) = iExtensionCount.ToString.PadLeft((iUpperBound + 1).ToString.Length, CChar("0")) & My.Resources.CharListDelimiter & currentExtension.ToString 'modify element to include count (padded with zeros to allow for sorting), delimiter and file extension
                End If

                iCurrentIndex += 1
                If iCurrentIndex = .Count Then Exit Do
            Loop

            .Sort() 'second numerical sort

            For i As Integer = 0 To .Count - 1
                sExtension = .Item(i).ToString
                iDelimeterIndex = sExtension.LastIndexOf(My.Resources.CharListDelimiter)
                .Item(i) = My.Resources.CharWildcard & My.Resources.CharExtensionDelimiter & sExtension.Substring(iDelimeterIndex + 1, sExtension.Length - iDelimeterIndex - 1)   'strip count and delimiter used for sorting
            Next
        End With

        oExtensions.TrimToSize()    'trim arraylist to actual number of elements to reduce memory overhead

        Return oExtensions

    End Function

    ''' <summary>
    ''' Returns an array of ListViewItems, used to populate ListView control.
    ''' </summary>
    ''' <param name="fileMask">File mask by which to filter returned items (format: *.xxx).</param>
    ''' <param name="returnHidden">Optional switch to return items marked as hidden from display.</param>
    ''' <returns>ListViewItem array from which ListView control can be populated.</returns>
    Public Function GetListViewItems(ByVal fileMask As String,
                                     Optional ByVal returnHidden As Boolean = False) As ListViewItem()


        Dim oListViewItems As New ArrayList
        Dim sFileMask As String = StripFileMask(fileMask)

        For i As Integer = 0 To iUpperBound
            If ((IsDisplayed(i) OrElse returnHidden) AndAlso (sFileMask.Equals(ListItemFilename(i, FilenameParseOption.ReturnExtension), StringComparison.CurrentCultureIgnoreCase) OrElse sFileMask.Equals(My.Resources.CharWildcard))) Then
                Dim oListItem As New ListViewItem
                oListItem.Tag = i   'used to track index of item within file list (useful when order or displayed status is changed)
                oListItem.Checked = IsChecked(i)
                With oListItem.SubItems
                    .Add(ListItemFilename(i))
                    .Add(ListItemPath(i))
                    .Add(FormatFileSize(ListItemSize(i), DefaultFileUnit))
                End With
                oListViewItems.Add(oListItem)
                IsDisplayed(i) = True
            Else
                IsDisplayed(i) = False
            End If
        Next

        Return DirectCast(oListViewItems.ToArray(GetType(ListViewItem)), ListViewItem())

    End Function

    ''' <summary>
    ''' Returns the total size in bytes of items in the FileList matching the specified parameters.
    ''' </summary>
    ''' <param name="params">Optional parameters by which to filter returned files.</param>
    ''' <returns>Total size of matching files in bytes</returns>
    ''' <remarks></remarks>
    Public Function GetTotalSize(Optional ByVal params As Short = ItemParamaters.Null) As Long        'return total size of list in bytes, optionally only include checked items

        Dim iSizeInBytes As Long

        For i As Integer = 0 To iUpperBound
            If (params And Parameters(i)) = params Then iSizeInBytes += ListItemSize(i)
        Next

        Return iSizeInBytes

    End Function

    ''' <summary>
    ''' Hides items in the current list that do not have a file in the specfied list with the specified extension (ignores file extensions in specified list).
    ''' </summary>
    ''' <param name="fileList">FileList against which to compare current list for orphaned files.</param>
    ''' <param name="searchMask">ile mask by which to search for orphaned files (format: *.xxx).</param>
    Public Sub HideOrphanedFiles(ByVal fileList As FileList,
                                 ByVal searchMask As String)

        If iUpperBound < 0 Then Exit Sub 'list is empty

        Dim iDuplicateIndex As Integer
        Dim iDuplicatesFound As Integer
        Dim iLastDuplicateIndex As Integer = -1
        Dim sMask As String = StripFileMask(searchMask)

        'set i / upper bound to index of last entry of search list >=
        'set start / lowerbound to index of first entry of search list <=

        'debugTimer = Stopwatch.StartNew

        For i As Integer = 0 To fileList.FileCount - 1    'loop for each item in main list
            If (fileList.Parameters(i) And (ItemParamaters.CheckedAndDisplayed)) = (ItemParamaters.CheckedAndDisplayed) Then    'ensure the item is checked and currently displayed to the user
                iDuplicateIndex = IndexOf(fileList.ListItemFilename(i, FilenameParseOption.ReturnFilename), sMask, iDuplicatesFound) 'get the index of the intance of the equivalent file matching the specified mask
                If iDuplicateIndex > -1 Then    'matching file found
                    IsDisplayed(iDuplicateIndex) = False    'hide matching instance from array
                    For j = iLastDuplicateIndex + 1 To iDuplicateIndex - 1
                        IsDisplayed(j) = True
                    Next
                    iLastDuplicateIndex = iDuplicateIndex
                    iDuplicatesFound += 1
                    'If iDuplicatesFound > iUpperBound Then Exit For 'all duplicate items have been located  '*** UNSURE IF THIS IS STILL REQUIRED
                    '*** BREAK CONDITION HERE TO CHECK IF ORPHANS = FILELIST DISPLAYEDCOUNT?
                End If
            End If
        Next

        For i As Integer = iLastDuplicateIndex + 1 To iUpperBound  'mark remaining items displayed
            IsDisplayed(i) = True
        Next

        'MessageBox.Show("Duplicates found: " + oOrphanArray.Count.ToString + "   Time taken: " + debugTimer.ElapsedTicks.ToString + "   j Loops: " + iLoops.ToString)
        'iLoops = 0

    End Sub

    ''' <summary>
    ''' Returns the index of a specified file in the FileList.  Implements a binary search with optional lower boundary.
    ''' </summary>
    ''' <param name="filename">Filename of item for which to return index.</param>
    ''' <param name="extension">Extension of item for which to return index.</param>
    ''' <param name="lowerBound">Optional index at which to commence search; items located at indexes below this will not be searched.</param>
    ''' <returns>Index of maching item if found, otherwise -1.</returns>
    Private Function IndexOf(ByVal filename As String,
                             ByVal extension As String,
                             Optional ByVal lowerBound As Integer = 0) As Integer

        Dim iUpperSearchBound As Integer = iUpperBound + 1
        Dim iLowerSearchBound As Integer = lowerBound
        Dim iCurrentElement As Integer

        'Dim intUpperBoundEstimate As Integer = intFileCount - FileList.FileCount
        'intUpperSearchBound = Math.Min(intFileCount + 1, intLowerSearchBound + intUpperBoundEstimate + intDuplicatesFound)      'narrow initial search field based on number of duplicates and difference in list sizes
        'intLowerSearchBound = intDuplicatesFound

        Do
            'iLoops += 1

            iCurrentElement = iLowerSearchBound + CInt(Math.Round((iUpperSearchBound - iLowerSearchBound) / 2, System.MidpointRounding.AwayFromZero)) - 1
            Select Case String.Compare(filename & My.Resources.CharExtensionDelimiter & extension, ListItemFilename(iCurrentElement), StringComparison.CurrentCultureIgnoreCase)
                Case -1
                    iUpperSearchBound = iCurrentElement 'string exists in lower half of array subset
                Case 0
                    Return iCurrentElement  'match located
                Case 1
                    iLowerSearchBound = iCurrentElement + 1 'string exists in upper half of array subset
            End Select

        Loop Until iLowerSearchBound = iUpperSearchBound

        Return -1   'no result found
    End Function

    ''' <summary>
    '''  Gets/sets the checked status of an item at the specified index.
    ''' </summary>
    ''' <param name="index">Index at which to get/set the property.</param>
    ''' <returns>True if item checked, otherwise false.</returns>
    Public Property IsChecked(ByVal index As Integer) As Boolean

        Get
            Return (Parameters(index) And ItemParamaters.Checked) = ItemParamaters.Checked
        End Get

        Set(ByVal value As Boolean)
            UpdateParameter(index, ItemParamaters.Checked, value, iSelectedCount)
        End Set

    End Property

    ''' <summary>
    '''  Gets/sets the displayed status of an item at the specified index.
    ''' </summary>
    ''' <param name="index">Index at which to get/set the property.</param>
    ''' <returns>True if item displayed, otherwise false.</returns>
    Public Property IsDisplayed(ByVal index As Integer) As Boolean

        Get
            Return (Parameters(index) And ItemParamaters.Displayed) = ItemParamaters.Displayed
        End Get

        Set(ByVal value As Boolean)
            UpdateParameter(index, ItemParamaters.Displayed, value, iDisplayedCount)
        End Set

    End Property

    ''' <summary>
    ''' Returns the filename, extension or both for an item at the specified index.
    ''' </summary>
    ''' <param name="index">Index of item for which filename is to be returned.</param>
    ''' <param name="params">Optional parameters to return filename, extension or both.</param>
    ''' <returns>Filename and/or extension for the item at the specified index</returns>
    Public Function ListItemFilename(ByVal index As Integer,
                                     Optional ByVal params As FilenameParseOption = FilenameParseOption.ReturnFilename Or FilenameParseOption.ReturnExtension) As String

        If iUpperBound < 0 Then Return Nothing

        Dim sElement As String = oFileArray(index).ToString
        Dim sLeftDelimiter As String = My.Resources.CharPathDelimiter
        Dim sRightDelimiter As String = My.Resources.CharListDelimiter
        Dim extensionPresent As Boolean = sElement.LastIndexOf(My.Resources.CharExtensionDelimiter) > sElement.LastIndexOf(My.Resources.CharPathDelimiter)

        If Not (FilenameParseOption.ReturnFilename Or FilenameParseOption.ReturnExtension) = (params And (FilenameParseOption.ReturnFilename Or FilenameParseOption.ReturnExtension)) Then
            If (FilenameParseOption.ReturnExtension = (params And FilenameParseOption.ReturnExtension)) And extensionPresent Then
                sLeftDelimiter = My.Resources.CharExtensionDelimiter
            ElseIf (FilenameParseOption.ReturnFilename = (params And FilenameParseOption.ReturnFilename)) And extensionPresent Then
                sRightDelimiter = My.Resources.CharExtensionDelimiter
            Else
                Return vbNullString
            End If
        End If

        Dim iLeftIndex As Integer = sElement.LastIndexOf(sLeftDelimiter)
        Return Mid(sElement, iLeftIndex + 2, sElement.LastIndexOf(sRightDelimiter) - iLeftIndex - 1)

    End Function

    ''' <summary>
    ''' Returns the path for an item at the specified index.
    ''' </summary>
    ''' <param name="index">Index of element for which file path is to be returned.</param>
    ''' <returns>File path for the item at the specified index.</returns>
    Public Function ListItemPath(ByVal index As Integer) As String

        Dim element As String = oFileArray(index).ToString
        Dim iLeftIndex As Integer = element.IndexOf(My.Resources.CharListDelimiter)

        Return Mid(element, iLeftIndex + 2, element.LastIndexOf(My.Resources.CharPathDelimiter) - iLeftIndex - 1)

    End Function

    ''' <summary>
    ''' Returns the file size in bytes of the item at the specified index.
    ''' </summary>
    ''' <param name="index">Index of element for which size is to be returned.</param>
    ''' <returns>File size in bytes.</returns>
    Private Function ListItemSize(ByVal index As Integer) As Long

        Dim element As String = oFileArray(index).ToString

        Return CLng(Right(element, Len(element) - element.LastIndexOf(My.Resources.CharListDelimiter) - 1))

    End Function

    ''' <summary>
    ''' Launches the default action or display the containing folder for the item at the specified index.
    ''' </summary>
    ''' <param name="index">Index of item for which file size is to be returned.</param>
    ''' <param name="openLocation">Optional switch to open the file location, rather than the file itself.</param>
    ''' <remarks></remarks>
    Public Sub OpenFile(ByVal index As Integer,
                             Optional ByVal openLocation As Boolean = False)

        Dim sPath As String = ListItemPath(index) & My.Resources.CharPathDelimiter & ListItemFilename(index)

        If FileErrorHandler.FileExists(sPath) Then
            Try
                If openLocation Then
                    Call Shell(My.Resources.ExplorerLaunchCommand & My.Resources.CharQuote & sPath & My.Resources.CharQuote, AppWinStyle.NormalFocus)
                Else
                    Process.Start(sPath)
                End If
            Catch ex As Exception
                FileErrorHandler.HandleFileError(ex)    'handle thrown error
            End Try
        End If

    End Sub

    ''' <summary>
    ''' Gets/sets the parameters of an item at the specified index.
    ''' </summary>
    ''' <param name="index">Index at which to get/set the property.</param>
    ''' <returns>Parameters for the specified index.</returns>
    Public Property Parameters(ByVal index As Integer) As ItemParamaters

        Get
            Return DirectCast([Enum].Parse(GetType(ItemParamaters), Left(oFileArray(index).ToString, oFileArray(index).ToString.IndexOf(My.Resources.CharListDelimiter))), ItemParamaters)
        End Get

        Set(ByVal params As ItemParamaters)
            Dim element As String = oFileArray(index).ToString
            oFileArray(index) = params.ToString & Right(element, Len(element) - element.IndexOf(My.Resources.CharListDelimiter, 1))
        End Set

    End Property

    ''' <summary>
    ''' Returns the parent directory of the FileList.
    ''' </summary>
    ''' <returns>The parent directory of the file list.</returns>
    ''' <remarks>This does not work for empty directorys - ListItemPath(0) doesn't work becauase oFileArray is empty</remarks>
    Public ReadOnly Property ParentDirectory As String

        Get
            'If sParentDirectory = vbNullString Then
            '    sParentDirectory = ListItemPath(0)
            '    For i = 1 To iUpperBound
            '        If String.Compare(sParentDirectory, ListItemPath(i), True) <> 0 Then
            '            Return ("(Multiple file paths)")    '  check for shortest string to determine top level? maybe populate at time of load
            '        End If
            '    Next
            'End If
            Return sParentDirectory
        End Get

    End Property

    ''' <summary>
    ''' Gets/sets the select all status of items marked as displayed. 
    ''' </summary>
    ''' <returns>True if all items marked as displayed are checked, otherwise false.</returns>
    Public Property SelectAll As Boolean

        Get
            If iDisplayedCount = FileCount(ItemParamaters.CheckedAndDisplayed) Then Return True Else Return False
        End Get

        Set(ByVal value As Boolean)
            For i As Integer = 0 To iUpperBound
                If IsDisplayed(i) Then IsChecked(i) = value
            Next
        End Set

    End Property

    ''' <summary>
    ''' Strips leading characters preceding '.' character of a file mask.
    ''' </summary>
    ''' <param name="fileMask">File mask to be stripped of leading characters.</param>
    ''' <returns>Input string stripped of all leading characters preceding file extension.</returns>
    Public Shared Function StripFileMask(ByVal fileMask As String) As String

        Dim iDelimiterIndex As Integer = fileMask.LastIndexOf(My.Resources.CharExtensionDelimiter)
        Return fileMask.Substring(iDelimiterIndex + 1, fileMask.Length - iDelimiterIndex - 1)

    End Function

    ''' <summary>
    ''' Sorts oFileArray and update count for checked/displayed items.
    ''' </summary>
    ''' <param name="params">Optional parameters that have perviously been applied to all files within the array to allow for updating of relevant counts.  Doesn't get or set parameters.</param>
    ''' <remarks>Used only at the time of creation of ArrayList oFileArray.</remarks>
    Private Sub UpdateParameter(Optional ByVal params As ItemParamaters = ItemParamaters.Null)

        With oFileArray
            .TrimToSize()   'trim arraylist to actual number of elements to reduce memory overhead
            .Sort()
            iUpperBound = .Count - 1
        End With

        If (params And ItemParamaters.Checked) = ItemParamaters.Checked Then iSelectedCount = iUpperBound + 1
        If (params And ItemParamaters.Displayed) = ItemParamaters.Displayed Then iDisplayedCount = iUpperBound + 1

    End Sub

    ''' <summary>
    ''' Updates parameter for item at specified index, and increment/decrement the specified tracking counter.
    ''' </summary>
    ''' <param name="index">Index of item for which parameter is to be updated.</param>
    ''' <param name="parameter">Parameter to be updated.</param>
    ''' <param name="value">New value to be apply to specified parameter.</param>
    ''' <param name="counter">Variable assigned to track count for specified parameter.</param>
    Private Sub UpdateParameter(ByVal index As Integer,
                                ByVal parameter As ItemParamaters,
                                ByVal value As Boolean,
                                ByRef counter As Integer)

        Dim newParams As ItemParamaters = Parameters(index)

        If value And Not (newParams And parameter) = parameter Then  'set to true, item is currently false
            newParams = newParams Or parameter
            counter += 1
        ElseIf Not value And (newParams And parameter) = parameter Then  'set to false, item is currently true
            newParams = newParams And Not parameter
            counter -= 1
        End If

        Parameters(index) = newParams

    End Sub

End Class